跳到主要内容

「KDOI-06-S」树上异或

· 阅读需 4 分钟

唉,今天模拟赛时瞪了2h2h没想到写法还因为忘取模导致暴力也没分了,T2T2爆零真是我上午分低的重要原因,因为先改完本题(甚至是自己写的)且思路清晰所以就先来改这个了,待会再补T1T1的题解

首先这道题大家应该都能想到是一个树上DPDP并且可以瞪出来∑∏⊕ci\sum \prod \oplus c_i很像树形背包的∑ci\sum c_i(好吧我没想到背包)

所以可以先写一下较暴力的44pts44pts,即设计状态fp,xf_{p,x}为以pp为根的子树中的异或和为xx的答案

转移方程就可以易得fp,k=∑i⊎j=kf[p][i]×f[x][j]+∑i=0Vf[p][k]+i×f[x][i]f_{p,k}=\sum_{i\uplus j=k} f[p][i]\times f[x][j]+\sum_{i=0}^{V} f[p][k]+i\times f[x][i](前面式子考虑不断边,后面考虑断边,VV是值域)

时间复杂度是O(nV2)O(nV^2),空间复杂度O(nV)O(nV),这样就已经可以拿到44pts44pts了

这时候我们发现时间和空间都爆炸了,所以考虑如何优化

因为我们做的是异或运算,所以其实每次操作后的对应位只和转移过程中的两个对应位有关

例如转移前aa的第ii位是AA,bb的第ii位是BB,则转移后得到数的第ii位为AA^BB,与其他位无关

这就启发我们拆位进行运算

所以设计状态fp,i,kf_{p,i,k}表示以pp为根的子树在第ii位是kk情况下的答案

考虑如何转移改点的权值和的数组,因为每个fp,i,k=xf_{p,i,k}=x能给第ii位提供xx的贡献,所以xorsump=∑i=0LOGV2i×fp,i,1xorsum_p=\sum_{i=0}^{LOGV} 2^i\times f_{p,i,1}

所以我们也就可以转移fp,i,kf_{p,i,k}了,因为0=00=0^0=10=1^1,1=01,1=0^1=11=1^00,所以可以直接对应位转移过来

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair< int,int >
#define QWQ return 0;

const int N=5e5+10, LOGV=60, mod=998244353;

ll val[N], xorsum[N];
ll f[N][LOGV][2];
vector< int > rode[N];

void dfs( int p,int fa )
{
for( int i=0;i<LOGV;i++ ) f[p][i][( val[p]>>i )&1]=1;//赋初始值,即原val值对应位i上是0/1数组的 [i][0/1]=1
for( auto &x:rode[p] )
{
if( x == fa ) continue;
dfs( x,p );
for( int i=0;i<LOGV;i++ )//每一位依次转移
{
ll f0=f[p][i][0], f1=f[p][i][1];//先记录数组的值,因为转移过程是该变两个的值导致现在的值失去转移意义
f[p][i][0]=( f0*f[x][i][0]+f1*f[x][i][1]+f0*xorsum[x] )%mod;//分别是(0^0)+(1^1)+(0+断边)
f[p][i][1]=( f1*f[x][i][0]+f0*f[x][i][1]+f1*xorsum[x] )%mod;//分别是(0^1)+(1^0)+(1+断边)
}
}
for( int i=0;i<LOGV;i++ ) ( xorsum[p]+=( 1ll<<i )%mod*f[p][i][1] )%=mod;//计算该点得到的权值和
}

int main()
{
// freopen( "xor.in","r",stdin );
// freopen( "xor.out","w",stdout );
ios::sync_with_stdio( false );
cin.tie( nullptr ), cout.tie( nullptr );
srand( time( 0 ) );
int n;
cin>>n;
for( int i=1;i<=n;i++ ) cin>>val[i];
for( int u=2, v;u<=n;u++ )
cin>>v,
rode[u].push_back( v ), rode[v].push_back( u );
dfs( 1,0 );
cout<<xorsum[1];
QWQ
}